﻿using ServerCommon.Log;
using ServerCommon.Utility;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using static ServerCommon.Net.NetClient;

using uid_t = System.Int64;
namespace ServerCommon.Net
{
    /// <summary>
    /// 服务器 服务器内部通信Buffer大小设置为 2M，处理客户端的clientService设置为 8K
    /// </summary>
    public class NetService : NetNode
    {
        private static LogHelper log = LogHelper.GetLogger(typeof(NetService));

        public ServerSettings ServerConfig => _serverSettings;
        private ServerSettings _serverSettings;

        private Socket _listenSocket;

        private ConcurrentStack<SocketAsyncEventArgs> _acceptSaeaPool;

        public NetService() : base()
        {
            _netType = EnumNodeNetType.Service;
        }

        public void ConfigSetting(ServerSettings settings)
        {
            _serverSettings = settings;
            _bufferManager = new BufferManager((long)settings.BufferSizeForEachBlock * settings.IOSAEANum, settings.BufferSizeForEachBlock);
            _ioSaeaPool = new ConcurrentStack<SocketAsyncEventArgs>();
            _acceptSaeaPool = new ConcurrentStack<SocketAsyncEventArgs>();
            _bufferManager.InitBuffer();
            CreateAcceptSAEAPool(settings.AcceptSAEANum);
            CreateIOSAEAPool(settings.IOSAEANum);
        }

        /// <summary>
        /// 作为服务器开始监听客户端连接
        /// </summary>
        /// <returns></returns>
        public EnumErrorCode StartListen()
        {
            EnumErrorCode ret;
            try
            {
                _listenSocket = new Socket(_serverSettings.LocalEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                _listenSocket.Bind(_serverSettings.LocalEndPoint);
                _listenSocket.Listen(_serverSettings.BackLog);
                PostAccept();
            }
            catch (SocketException ex)
            {
                log.ErrorFormat("StartListen failed SocketException : {0} , ErrorCode: {1} ", ex.Message, ex.ErrorCode);
            }
            catch (SecurityException ex)
            {
                log.ErrorFormat("StartListen failed SecurityException : {0} ", ex.Message);
            }
            catch (Exception ex)
            {
                log.ErrorFormat("StartListen failed Exception : {0} ", ex.Message);
            }
            finally
            {
                ret = EnumErrorCode.ListenFail;
            }
            return ret;
        }

        public void Config(NetNodeManager manager, EnumNodeServiceType serviceType, string ip, int port, int maxConnection, int blockSize, long nodeId)
        {
            Config(manager);
            _serviceType = serviceType;
            _nodeId = nodeId;   //这里的nodeId是自己进程的id
            ConfigSetting(new ServerSettings(ip, port, maxConnection, blockSize));
            _addr = ServerConfig.LocalEndPoint;
            manager?.Add(this);
        }

        private void CreateAcceptSAEAPool(int acceptSAEANum)
        {
            for (int i = 0; i < acceptSAEANum; i++)
            {
                _acceptSaeaPool.Push(CreateAcceptEventArgs());
            }
        }

        private SocketAsyncEventArgs CreateAcceptEventArgs()
        {
            SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs();
            acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed);
            return acceptEventArg;
        }

        private void Accept_Completed(object sender, SocketAsyncEventArgs acceptEventArgs)
        {
            ProcessAccept(acceptEventArgs);
        }

        private void HandleBadAccept(SocketAsyncEventArgs acceptEventArgs)
        {
            try
            {
                ResetSAEAObject(acceptEventArgs);
                ReleaseAccept(acceptEventArgs);
            }
            catch (Exception ex)
            {
                log.ErrorFormat("HandleBadAccept error: {0}", ex.Message);
            }
        }

        private void ReleaseAccept(SocketAsyncEventArgs acceptEventArgs, bool isRelease = true)
        {
            _acceptSaeaPool.Push(acceptEventArgs);
        }

        private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
        {
            try
            {
                if (acceptEventArgs.SocketError != SocketError.Success)
                {
                    HandleBadAccept(acceptEventArgs);
                    log.ErrorFormat("processAccept error:{0} {1}", acceptEventArgs.SocketError, acceptEventArgs.SocketError.ToString());
                }
                else
                {
                    if (!_ioSaeaPool.TryPop(out var ioEventArgs))
                    {
                        System.Diagnostics.Debug.Assert(false);
                    }
                    ioEventArgs.AcceptSocket = acceptEventArgs.AcceptSocket;
                    NetSession session = new NetSession(acceptEventArgs.AcceptSocket, NetSessionConfig.GetConfigByNodeType(_serviceType), Guid.NewGuid(), _serviceType);
                    session._localNodeId = _nodeId;

                    dictSession.Add(session.HashCode, session);

                    DataToken data_token = (DataToken)ioEventArgs.UserToken;
                    data_token.Session = session;

                    acceptEventArgs.AcceptSocket = null;
                    ReleaseAccept(acceptEventArgs);

                    // optimize 顺序有问题
                    _manager.PushIoEvent(new ConnectionEventArgs()
                    {
                        EventType = EnumConnectionEventType.Accept,
                        Session = session,
                        NodeId = _nodeId,
                    });

                    PostReceive(ioEventArgs);

                    log.InfoFormat("ProcessAccept, client addr:{0} , service remain saeacount :{1}", session.RemoteEndPoint.ToString(), _ioSaeaPool.Count);
                }
            }
            catch (Exception ex)
            {
                log.ErrorFormat("ProcessAccept error: {0} ", ex.Message);
            }
            finally
            {
                PostAccept();
            }
        }

        private void PostAccept()
        {
            try
            {
                if (!_acceptSaeaPool.TryPop(out var acceptEventArgs))
                {
                    acceptEventArgs = CreateAcceptEventArgs();
                }
                var willRaiseEvent = _listenSocket.AcceptAsync(acceptEventArgs);
                if (!willRaiseEvent)
                {
                    ProcessAccept(acceptEventArgs);
                }
            }
            catch (Exception e)
            {
                log.ErrorFormat("Post accept error: {0} stackTrace: {1}", e.Message, e.StackTrace);
            }
        }

        public override void Start()
        {
            StartListen();
        }
        public override void Stop()
        {
            base.Stop();

            foreach(var item in dictSession)
            {
                CloseClientSocket(item.Value);
            }
            _listenSocket.Close();
        }

        public EnumSendProtocolReturn SendProtocolToAll(Pb.IProtocol proto)
        {
            var data = proto.Serialize();
            foreach (var item in dictSession)
            {
                SendMessageAsync(item.Value, data);
            }
            return EnumSendProtocolReturn.OK;
        }
    }
}
